//Program by:
//Ben Schmidt - bjs778 - 11054018
//Mike Nimeck - mfn028 - 11053045
//Jeff Huang - jlh105 - 11050226
//
//Fully implemented by hand, but OpenGL SuperBible example
//used heavily as reference

//includes
#include <stdlib.h>
#include <time.h>

#include <GLTools.h>
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>

#include "main.h"
#include "fireworks.h"
#include "utils.h"
#include "glUtils.h"

#ifndef GLintptr_defined
    typedef ptrdiff_t GLintptr;
#endif

int MAX_NUM_FIREWORKS = 10;
int nextFirework = 0;

float MAX_INITIAL_FIREWORK_SPEED = 12.0f;
float MAX_X_SPEED = 12.0f;
float MAX_Z_SPEED = 12.0f;

float FIREWORK_VELOCITY_BIAS[] = {0.0f, MAX_INITIAL_FIREWORK_SPEED, 0.0f};


float FIREWORK_SCALE_X = 12.0f;
float FIREWORK_SCALE_Y = 12.0f;
float FIREWORK_SCALE_Z = 12.0f;
float FIREWORK_SCALE[] = {FIREWORK_SCALE_X, FIREWORK_SCALE_Y, FIREWORK_SCALE_Z};

float FIREWORK_FUZZINESS = 0.3f;

void init_particles_fireworks(float* positions, 
		    unsigned int num_positions, 
		    const float *scale, 
		    const float *bias)
{
    
    int i;
    float* p = positions;
    
    //NOTE: *p++ uses *p for the expression, 
    // then updates p to the next element.
    if (bias){
	for (i = 0; i < num_positions; i++){
	    *p++ = ((random_float() * 2) - 1) * scale[0] + bias[0];
	    *p++ = ((random_float() * 2) - 1) * scale[1] + bias[1];
	    *p++ = ((random_float() * 2) - 1) * scale[2] + bias[2];

	}
    }
    else{
	for (i = 0; i < num_positions; i++){
	    *p++ = FIREWORKS_X;
	    *p++ = FIREWORKS_Y;
	    *p++ = CALCULATED_DEPTH;
	  
	}
    }
}


//initialize the fireworks particle system
void initFireworks(){    
    //allocate memory for the position data
    //multiply by 3 for x, y, z
    float* positions = new float[FIREWORK_PARTICLES * MAX_NUM_FIREWORKS * 3];
    
    
    //
    //set up the first update VAO
    //  
    
    //initialize particle positions	
    init_particles_fireworks(&positions[nextFirework], FIREWORK_PARTICLES, NULL, NULL); 
    
    //set up position stuff
    glBindVertexArray(update_vao[0]);
    glBindBuffer(GL_ARRAY_BUFFER, particle_position[frame_index % 2]);
    glBufferData(GL_ARRAY_BUFFER, FIREWORK_PARTICLES * MAX_NUM_FIREWORKS * 3 * sizeof(float), positions, GL_DYNAMIC_COPY);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(0);
    
    //initialize velocities
    init_particles_fireworks(&positions[nextFirework], FIREWORK_PARTICLES, FIREWORK_SCALE, FIREWORK_VELOCITY_BIAS);
    glBindBuffer(GL_ARRAY_BUFFER, particle_velocity[frame_index % 2]);
    glBufferData(GL_ARRAY_BUFFER, FIREWORK_PARTICLES  * MAX_NUM_FIREWORKS * 3 * sizeof(float), positions, GL_DYNAMIC_COPY);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(1);
    
    //initialize colors
    glBindBuffer(GL_ARRAY_BUFFER, firework_colors);
    float red[3] = {1.0f, 0.0f, 0.0f};
    init_particles(&positions[nextFirework], FIREWORK_PARTICLES, 0.4f, red);
    glBufferData(GL_ARRAY_BUFFER, FIREWORK_PARTICLES * MAX_NUM_FIREWORKS * 3 * sizeof(float), positions, GL_DYNAMIC_COPY);
    glVertexAttribPointer(5, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(5);

    nextFirework++;
    
    //everything is now on the GPU. Crazy. Delete the data from memory.
    delete[] positions;
}

// Update is run everytime main.cpp/render_scene is called from the main OpenGL call
void fireworks_update(){
    GLintptr offset = FIREWORK_PARTICLES * nextFirework * 3 * sizeof(float);
    int length = FIREWORK_PARTICLES * 3 * sizeof(float);
    void* ptr;
    float *positions;
    
    glBindBuffer(GL_ARRAY_BUFFER, particle_position[frame_index % 2]);
    
    ptr = glMapBufferRange(GL_ARRAY_BUFFER, offset, length, GL_MAP_WRITE_BIT);  
    positions = (float*)ptr;
	
    init_particles_fireworks(positions, FIREWORK_PARTICLES, NULL, NULL); 
	
    // When we're done with the buffer, unmap it.
    glUnmapBuffer(GL_ARRAY_BUFFER);
        
    
    glBindBuffer(GL_ARRAY_BUFFER, particle_velocity[frame_index % 2]);
    
    ptr = glMapBufferRange(GL_ARRAY_BUFFER, offset, length, GL_MAP_WRITE_BIT );
    
    positions = (float*)ptr;
    
    init_particles_fireworks(positions, FIREWORK_PARTICLES,  FIREWORK_SCALE, FIREWORK_VELOCITY_BIAS);
    
    
    glUnmapBuffer( GL_ARRAY_BUFFER);

    int color = (int)(random_float() * 10) % 10;
    float fuzzinessSub = FIREWORK_FUZZINESS / 2;
    float firework_color[3];
    
    if (color == 0){
	//goal is pink!
	firework_color[0] = 0.1f - fuzzinessSub;
	firework_color[1] = 0.25f - fuzzinessSub;
	firework_color[2] = 0.5f - fuzzinessSub;
    }
    else if (color == 1){
	//goal is red!
	firework_color[0] = 1.0f - fuzzinessSub;
	firework_color[1] = 0.0f - fuzzinessSub;
	firework_color[2] = 0.0f - fuzzinessSub;
    }
    else if (color == 2){
	//goal is orange!
	firework_color[0] = 1.0f - fuzzinessSub;
	firework_color[1] = 0.7f - fuzzinessSub;
	firework_color[2] = 0.2f - fuzzinessSub;
    }
    else if (color == 3){
	//goal is yellow!
	firework_color[0] = 1.0f - fuzzinessSub;
	firework_color[1] = 1.0f - fuzzinessSub;
	firework_color[2] = 0.2f - fuzzinessSub;
    }
    else if (color == 4){
	//goal is green!
	firework_color[0] = 0.3f - fuzzinessSub;
	firework_color[1] = 1.0f - fuzzinessSub;
	firework_color[2] = 0.0f - fuzzinessSub;
    }
    else if (color == 5){
	//goal is cyan!
	firework_color[0] = 0.0f - fuzzinessSub;
	firework_color[1] = 1.0f - fuzzinessSub;
	firework_color[2] = 1.0f - fuzzinessSub;
    }
    else if (color == 6){
	//goal is blue!
	firework_color[0] = 0.0f - fuzzinessSub;
	firework_color[1] = 0.0f - fuzzinessSub;
	firework_color[2] = 1.0f - fuzzinessSub;
    }
    else if (color == 7){
	//goal is violet!
	firework_color[0] = 0.5f - fuzzinessSub;
	firework_color[1] = 0.1f - fuzzinessSub;
	firework_color[2] = 0.9f - fuzzinessSub;
    }
    else if (color == 8){
	//goal is white!
	firework_color[0] = 1.0f - fuzzinessSub;
	firework_color[1] = 1.0f - fuzzinessSub;
	firework_color[2] = 1.0f - fuzzinessSub;
    }
    else if (color == 9){
	//goal is electric blue!
	firework_color[0] = 0.25f - fuzzinessSub;
	firework_color[1] = 0.5f - fuzzinessSub;
	firework_color[2] = 1.0f - fuzzinessSub;
    }
    
    if (firework_color[0] < 0.0f){
	firework_color[0] = 0.0f;
    }
    if (firework_color[1] < 0.0f){
	firework_color[1] = 0.0f;
    }
    if (firework_color[2] < 0.0f){
	firework_color[2] = 0.0f;
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, firework_colors);
    
    ptr = glMapBufferRange(GL_ARRAY_BUFFER, offset, length, GL_MAP_WRITE_BIT);
    
    positions = (float*)ptr;
    
    init_particles(positions, FIREWORK_PARTICLES, FIREWORK_FUZZINESS, firework_color);
    
    glUnmapBuffer(GL_ARRAY_BUFFER);
    
    if (nextFirework++ > MAX_NUM_FIREWORKS - 2){
	nextFirework = 0;
    }      
}

// Starts a new fireworks cluster on the space where the left-mouse button is clicked on screen
void fireworksMouseAction(int button, int state, int x, int y){  
  float xx = (float)x;
  float yy = (float)y;
  float zz = FIREWORKS_DEPTH;
  
    windowToWorld(xx, yy, zz);	
    
    FIREWORKS_X = xx;
    FIREWORKS_Y = yy;
    CALCULATED_DEPTH = zz;
    
    if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)){
      
	fireworks_update();	    
}
    if (button == 3){
	FIREWORKS_DEPTH -= FIREWORKS_DEPTH_SPEED;
	if(FIREWORKS_DEPTH < NEAR_MAX){
	    FIREWORKS_DEPTH = NEAR_MAX;
	}
	printf("Depth changed to: %f\n", CALCULATED_DEPTH);
    }
    else if (button == 4){
	FIREWORKS_DEPTH += FIREWORKS_DEPTH_SPEED;
	if(FIREWORKS_DEPTH > FAR_MAX){
	    FIREWORKS_DEPTH = FAR_MAX;
	}	
	printf("Depth changed to: %f\n", CALCULATED_DEPTH);
    }

 
}


